Section top bar
canonComposable bar with title, chips, search, dividers and spacer.
Four screens hand-roll the identity instead of SectionTopBarTitle — decision A6. Phone two-step (AD 27 Sep 2026, acrm !3997, fix/nastaveni-mobil-dvoukrok): Nastavení (SettingsLayout.tsx) below the 720px breakpoint drops the side rail and becomes ⌘K's own two-step — `/nastaveni` with no section shows only the grouped menu full-width (`.nastaveni-page.m-menu`), `/nastaveni/<sekce>` shows only that section's `SectionTopBar` (`flat`) and body (`.m-section`) with a quiet `IconButton icon="arrow-left"` (`.set-back`) as the bar's first item, wired to `navigate('/nastaveni')`. Desktop keeps rail + content side by side unchanged. Side rail at ≥720px would take ~260px on a 390px phone and clip every section's fields — the same two-step shape as the ⌘K palette's list→detail step, not a new pattern. acrm !4029 (27 Sep 2026): new app-only layer apps/web/src/ds/section-top-bar.css (home for bar rules; ds.css is over the 100 kB ratchet and must not grow) — a long name in the bar stays on one line with an ellipsis and SectionTopBarTitle carries `title` with the full name; switch labels in the bar do not wrap. acrm !4044 (27 Sep 2026): the record view switcher on person/company (Activities / Profile) is the canonical `.tchip` with chevron (RULES §8: a switcher in the bar = .tchip + menu), the bespoke `.rvsw-*` was deleted. Menu items without description text (acrm !4051). Phone layout, Attio pattern (owner 2026-09-28, acrm !4088): actions as data — primaryAction {label, icon, onClick, kind} → Fab below 720 px (round only for kind 'create'), secondaryActions (menuOnly, danger, phoneOnly) → one ⋮ bottom sheet, closeAction last; row 1 = title + ⋮, row 2 = tools (view picker left, Filtr/Řadit right). Desktop unchanged. PageHeader forwards the same props. 14 Oct 2026 (owner "schvaleno" 24b v4, proto/detail-jednotne@9600c16a2, acrm !5055; RULES §26): record detail header = ↑↓ · name · ⋮ · ✕ with NO blue primary. Rare actions stay in context (Nový obchod in Nejbližší aktivita + Obchody tab; project Aktualizace in its section); an order shows one button only for the step it waits for. Dev check 14 Oct 1440 + 390: person, company, project, order — 0 primaries in the header, the project label chip gone. Registry repaired 17 Oct 2026 (review F5): PageHeader, RecordCloseButton live in apps/web (see files), not exported by @acrm/ui; dropped from react[]. 20 Oct 2026 (ACRM-569, proto/komunikace-menu-zobrazit-vse@0b7180f3a): `SectionTopBarTitle dot` — identity by colour with no glyph (`.pipe-dot`, a 12 px circle inside the `--tile-sm` frame, CSS in apps/web/src/ds/section-top-bar-dot.css); used by a mail label list. Story: "With dot".
Used in 64 files · 56 areas · 118× — components/ListSearch, components/accounting, components/agent-desk, components/datamart, components/segment, pages/AccountingInvoiceDetailPage …
@acrm/ui <SectionTopBar> <SectionTopBarChip> <SectionTopBarSearch> <SectionTopBarDivider> <SectionTopBarSpacer>.tbar .tchip .acrm-input .vspace +7SourceStoryLegacy card Section top bar.htmlOne bar above every list and every record. It answers, in this order: what am I looking at (identity), what changes what I see (view, filter, search), and what can I do (quiet actions, at most one primary). packages/ui/src/components/SectionTopBar.tsx — SectionTopBar, SectionTopBarChip, SectionTopBarSearch, SectionTopBarDivider, SectionTopBarSpacer; classes .tbar*, .tchip, .vspace in apps/web/src/ds.css. Simple list screens use the PageHeader wrapper.
The bar is opinionated on purpose, and the component warns in development when its rules are broken.
When to use
- Above any list, any record, any section screen — one screen, one bar.
- To host the screen's view switch (
NavSelect), its filter and sort chips, its search and its actions.
When not to use
- As the application frame — that is Top bar (
.as-top), which never changes with the screen. - As a tab row — that is Tab strip, which goes under this bar.
- As a place for a count, a breadcrumb, or a status sentence.
Anatomy
- Bar
.tbar: one row, hairline underneath (.tbar.flatdrops it), slots in fixed order — identity · view/filter controls ·SectionTopBarSpacer(.vspace) · actions ·primaryAction. - Identity:
SectionTopBarTitle— exactly one of icon, tile+tone, avatar, heading-dropdown, inline rename (see its own page). - Chip
.tchip:height: var(--control-h)(30px),padding: 0 10px,gap: 7px,radius-md,var(--small), icon--ico-xs; muted key.k, bold value.v, chevron.chev, open state.tchip.open. - Search
.tsearch: quiet box that paints its border accent on:focus-within; the input itself carries no ring. - Divider
.tdiv(1×20px hairline), spacer.vspace(flex: 1). - Actions are quiet (icon + verb); the single
primaryActionis the only filled button.
Identity by colour — dot (2026-10-20)
SectionTopBarTitle dot="var(--gc-…)" draws a 12 px colour circle inside the tile frame, with no glyph. Use it when the screen's identity is a colour the user chose (a mail label), not a type. Needs no icon; do not combine with tileIcon, kind or avatar.
Phone (below 720 px) — Attio pattern (2026-09-28, acrm !4088)
Only for a bar whose actions are data (primaryAction as { label, icon, onClick, kind? }, secondaryActions, closeAction; .tbar.has-acts):
- Row 1: title (ellipsis) + ⋮ „Další akce" +
closeAction(×). No text buttons. - ⋮ opens one bottom sheet with all secondary actions (
forceMenu: even a single item opens the sheet);dangerrows are red;phoneOnlyactions appear only here (e.g. „Uložit pro všechny"). - Row 2 (view row, owner-approved 28 Sep 2026, acrm !4114): one view picker on the left (
NavSelectchip — „Zobrazení ⌄" on Úkoly with Kompaktní / Plné instead of the segmented switch, „Schránka ⌄" on Pošta), icon buttons on the right. Each opens a bottom sheet with a check at the chosen option. - Filtr carries the neutral count of conditions (
FilterToggleChip count). - Seskupit and Řadit carry an accent dot, not a count, when they are off their default (
.fb-chip.lst-toggle.tb-dot.set::after, inside the icon's corner so the scrolling tools row does not clip it). A count would widen the icon and the row would overflow at 390 px. The chosen option lives in the accessible name and the tooltip („Řadit: Termín", „Seskupit: Podle stavu"). - Search is an icon on the right; a tap expands it over the whole row (
.tsearch-fill) with × to close; with a typed query it stays open. The view picker steps aside while search is open. - Quiet desktop tool icons that do not fit (Pošta: Synchronizovat, Spravovat složky) move into ⋮ as
phoneOnlysecondary actions. - The primary becomes the
Fabbottom-right (round only forkind: 'create', otherwise extended with its label). - Desktop is unchanged: secondary actions are buttons before the primary,
menuOnlyactions sit in ⋮,closeActionstays last. - A bar still passing a JSX primary keeps it in row 1 until it is converted.
Best practices
- Give the bar exactly one identity mark — it never starts with bare text.
- Put everything that changes the view before the spacer, and everything that acts after it.
- Keep at most one primary action, and let it carry the verb of the screen (
Nový obchod). - Let the bar scroll sideways on a narrow screen (≤720px) rather than squeezing its controls; the primary action sticks to the right edge.
- A count or "N z M" in the bar — that belongs to the listing footer or a stat tile. (Record navigation arrows are the one documented exception.)
- A breadcrumb inside the identity.
- Coloured or filled action buttons other than the one primary.
- Capitals, and no second bar under this one except the Filter bar or a Tab strip.
- A hand-built
.tbarwhen the component would do; the escape hatch exists (status star, logo trigger, colour swatch) but it skips every guardrail.
Content
- Identity = the name of the thing, sentence case, no type prefix.
- Chips read
key value(Řadit Vytvořeno,Filtr 2). - Actions are verbs; the primary action names what will be created.
Related
title-with-entity-tile— the identity of this bar.filter-bar— the second row under it.tab-strip,record-tabs— the tab row under a record's bar.top-bar— the application frame, not this.- Stories:
components--section-top-bar--default,--variants,--states,--with-icon-and-kbd,--composition; legacy overviewcomponents--section-top-bar.
Record header on the phone (owner approved 1. 10. 2026, acrm !4362, verified on dev)
- ≤720 px the record header (person, company, deal, task, project…) is ONE row of the normal bar height: ‹ back (44 px, „Zpět na výpis“) · mark + name (truncates) · ⋯ (44 px). The ✕ is not drawn on the phone — ‹ has the same target, Esc too. Desktop unchanged.
- The view switch (Aktivity / Profil) and reservation actions (Rezervovat / Uvolnit / Převzít) are the first items of ⋯ (
RecordActionsMenuleadItems): choices with a tick on the active one, then a hairline, then the record actions. This is an approved exception to the MenuButton rule „choices all or none“ — the hairline separates the two groups (Linear does the same). - Reservation state lives as a row „Rezervace“ in Detail záznamu, not in the header.
- Pattern source: Attio person (one row ‹ · avatar + name · ⋮) and Linear issue (‹ · ✎ · ⋯). Two-row header was rejected (takes too much space).
- The header stays on top while the card scrolls (sticky, acrm !4369).
- Extended to meeting, task, project (acrm !4394, verified on dev 1. 10. 2026): same one row ‹ · mark + name · ⋯ on the phone, sticky; meeting ⋯ starts with „Zeptat se AI nad touto schůzkou“; task has no ⋯ (no header actions). Team member in Settings: ✕ hidden on the phone, back is the Settings bar „← Nastavení“ only.
- Menu items that cannot act yet are hidden, not disabled (no tooltip on touch): meeting exports appear only when minutes / a finished transcript exist.
Source: docs/section-top-bar/section-top-bar.usage.md · GitLab